/**
 * @(#)TypeAnalyzer.java, 2008-5-20.
 * Copyright 2008  Program Analysis Research Group(PARG),Institute of Software, Peking University.
 * All rights reserved.
 */
package edu.pku.sei.pgie.analyzer.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

import org.eclipse.jdt.core.dom.ArrayType;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.QualifiedType;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.WildcardType;

/**
 * @author HeLi
 */
public class TypeAnalyzer {
    public static class TypeInfo {
        public String typeName;

        public boolean isGeneric;

        public boolean isContainer;

        public boolean isSimpleType;

        public boolean isContraint;

        public boolean isUpperBound;

        public List<TypeInfo> childTypes;

        public TypeInfo() {
            childTypes = new ArrayList<TypeInfo>();
            isGeneric = false;
            isContainer = false;
            isSimpleType = false;
            isContraint = false;
            isUpperBound = false;
        }

        public boolean isContainer() {
            if (isContainer)
                return true;
            for (TypeInfo ct: childTypes) {
                if (ct.isContainer) {
                    return true;
                }
            }
            return false;
        }

        public String[] getContainerElementTypes() {
            HashSet<String> results = new HashSet<String>();
            if (isContainer) {
                for (TypeInfo childType: childTypes) {
                    results.add(childType.typeName);
                }
            }
            for (TypeInfo childType: childTypes) {
                String[] elementTypes = childType.getContainerElementTypes();
                for (String elementType: elementTypes) {
                    results.add(elementType);
                }
            }
            return results.toArray(new String[results.size()]);
        }

        public String[] getReferencedTypes() {
            HashSet<String> results = new HashSet<String>();
            results.add(typeName);
            for (TypeInfo childType: childTypes) {
                String[] childReferencedTypes = childType.getReferencedTypes();
                for (String type: childReferencedTypes) {
                    results.add(type);
                }
            }
            return results.toArray(new String[results.size()]);
        }
    }

    public static HashSet<String> BUILDIN_TYPES = new HashSet<String>();
    static {
        BUILDIN_TYPES.add("java.lang.Integer");
        BUILDIN_TYPES.add("lava.lang.String");
        BUILDIN_TYPES.add("java.lang.Float");
        BUILDIN_TYPES.add("java.lang.Double");
        BUILDIN_TYPES.add("java.lang.Long");
        BUILDIN_TYPES.add("java.lang.Char");
        BUILDIN_TYPES.add("java.lang.Byte");
        BUILDIN_TYPES.add("java.lang.Short");
    }

    public static HashSet<String> COLLECTION_TYPES = new HashSet<String>();
    static {
        COLLECTION_TYPES.add("java.util.Collection");
        COLLECTION_TYPES.add("java.util.List");
        COLLECTION_TYPES.add("java.util.Set");
        COLLECTION_TYPES.add("java.util.Queue");
        COLLECTION_TYPES.add("java.util.AbstractCollection");
        COLLECTION_TYPES.add("java.util.AbstractList");
        COLLECTION_TYPES.add("java.util.AbstractQueue");
        COLLECTION_TYPES.add("java.util.ArrayList");
        COLLECTION_TYPES.add("java.util.Vector");
        COLLECTION_TYPES.add("java.util.Stack");
        COLLECTION_TYPES.add("java.util.TreeSet");
        COLLECTION_TYPES.add("java.util.HashSet");
        COLLECTION_TYPES.add("java.util.SortedSet");
    }

    public static HashSet<String> MAP_TYPES = new HashSet<String>();
    static {
        MAP_TYPES.add("java.util.Map");
        MAP_TYPES.add("java.util.SortedMap");
        MAP_TYPES.add("java.util.AbstractMap");
        MAP_TYPES.add("java.util.HashMap");
        MAP_TYPES.add("java.util.TreeMap");
    }

    private HashMap<String, TypeInfo> cachedTypes;

    public TypeAnalyzer() {
        cachedTypes = new HashMap<String, TypeInfo>();
    }

    public TypeInfo analyze(Type type) {
        TypeInfo typeInfo = new TypeInfo();
        if (type instanceof PrimitiveType) {
            PrimitiveType primitiveType = (PrimitiveType) type;
            typeInfo.typeName = primitiveType.getPrimitiveTypeCode().toString();
            if (typeInfo.typeName.equals("void")) {
                return null;
            }
            typeInfo.isContainer = false;
            typeInfo.isGeneric = false;
            typeInfo.isSimpleType = true;
            return typeInfo;
        } else if (type instanceof SimpleType || type instanceof QualifiedType) {
            ITypeBinding typeBinding = type.resolveBinding();
            String typeName = typeBinding.getQualifiedName();
            if (cachedTypes.containsKey(typeName)) {
                return cachedTypes.get(typeName);
            }
            typeInfo.typeName = typeName;
            typeInfo.isSimpleType = true;
            typeInfo.isContainer = isContainer(typeBinding);
            cachedTypes.put(typeInfo.typeName, typeInfo);
        } else if (type instanceof ArrayType) {
            typeInfo.isContainer = true;
            ArrayType arrayType = (ArrayType) type;
            TypeInfo elementTypeInfo = analyze(arrayType.getElementType());
            StringBuffer arrayName = new StringBuffer();
            arrayName.append(elementTypeInfo.typeName);
            for (int i = 0; i < arrayType.getDimensions(); i++) {
                arrayName.append("[]");
            }
            typeInfo.typeName = arrayName.toString();
            typeInfo.childTypes.add(elementTypeInfo);
        } else if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            typeInfo = analyze(parameterizedType.getType());

            List typeParameters = parameterizedType.typeArguments();
            for (int i = 0; i < typeParameters.size(); i++) {
                Type typeArgument = (Type) typeParameters.get(i);
                typeInfo.childTypes.add(analyze(typeArgument));
            }
        } else if (type instanceof WildcardType) {
            WildcardType wildcardType = (WildcardType) type;
            Type bound = wildcardType.getBound();
            TypeInfo boundInfo = analyze(bound);
            boundInfo.isContraint = true;
            boundInfo.isUpperBound = wildcardType.isUpperBound();
            return boundInfo;
        }
        return typeInfo;
    }

    private boolean isContainer(ITypeBinding typeBinding) {
        String qualifiedName = typeBinding.getQualifiedName();
        if (qualifiedName.equals("java.lang.Object")) {
            return false;
        }
        if (COLLECTION_TYPES.contains(qualifiedName)
                || MAP_TYPES.contains(qualifiedName)) {
            return true;
        } else {
            ITypeBinding superBinding = typeBinding.getSuperclass();
            if (superBinding != null && isContainer(superBinding)) {
                return true;
            }
            ITypeBinding[] interfaceBindings = typeBinding.getInterfaces();
            for (ITypeBinding interfaceBinding: interfaceBindings) {
                if (isContainer(interfaceBinding)) {
                    return true;
                }
            }
            return false;
        }
    }

    public String getNameValue(Name name) {
        if (name instanceof SimpleName) {
            SimpleName sn = (SimpleName) name;
            return sn.getFullyQualifiedName();
        } else {
            QualifiedName qualifiedName = (QualifiedName) name;
            if (qualifiedName.getQualifier() == null) {
                return qualifiedName.getName().getFullyQualifiedName();
            } else {
                return getNameValue(qualifiedName.getQualifier()) + "."
                        + qualifiedName.getName().getFullyQualifiedName();
            }
        }

    }

    public void clearCache() {
        cachedTypes.clear();
    }
}
